// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/interpreter/control-flow-builders.h"
#include "src/objects-inl.h"

namespace v8 {
namespace internal {
    namespace interpreter {

        BreakableControlFlowBuilder::~BreakableControlFlowBuilder()
        {
            BindBreakTarget();
            DCHECK(break_labels_.empty() || break_labels_.is_bound());
            if (block_coverage_builder_ != nullptr) {
                block_coverage_builder_->IncrementBlockCounter(
                    node_, SourceRangeKind::kContinuation);
            }
        }

        void BreakableControlFlowBuilder::BindBreakTarget()
        {
            break_labels_.Bind(builder());
        }

        void BreakableControlFlowBuilder::EmitJump(BytecodeLabels* sites)
        {
            builder()->Jump(sites->New());
        }

        void BreakableControlFlowBuilder::EmitJumpIfTrue(
            BytecodeArrayBuilder::ToBooleanMode mode, BytecodeLabels* sites)
        {
            builder()->JumpIfTrue(mode, sites->New());
        }

        void BreakableControlFlowBuilder::EmitJumpIfFalse(
            BytecodeArrayBuilder::ToBooleanMode mode, BytecodeLabels* sites)
        {
            builder()->JumpIfFalse(mode, sites->New());
        }

        void BreakableControlFlowBuilder::EmitJumpIfUndefined(BytecodeLabels* sites)
        {
            builder()->JumpIfUndefined(sites->New());
        }

        void BreakableControlFlowBuilder::EmitJumpIfNull(BytecodeLabels* sites)
        {
            builder()->JumpIfNull(sites->New());
        }

        LoopBuilder::~LoopBuilder()
        {
            DCHECK(continue_labels_.empty() || continue_labels_.is_bound());
        }

        void LoopBuilder::LoopHeader()
        {
            // Jumps from before the loop header into the loop violate ordering
            // requirements of bytecode basic blocks. The only entry into a loop
            // must be the loop header. Surely breaks is okay? Not if nested
            // and misplaced between the headers.
            DCHECK(break_labels_.empty() && continue_labels_.empty());
            builder()->Bind(&loop_header_);
        }

        void LoopBuilder::LoopBody()
        {
            if (block_coverage_builder_ != nullptr) {
                block_coverage_builder_->IncrementBlockCounter(block_coverage_body_slot_);
            }
        }

        void LoopBuilder::JumpToHeader(int loop_depth)
        {
            // Pass the proper loop nesting level to the backwards branch, to trigger
            // on-stack replacement when armed for the given loop nesting depth.
            int level = Min(loop_depth, AbstractCode::kMaxLoopNestingMarker - 1);
            // Loop must have closed form, i.e. all loop elements are within the loop,
            // the loop header precedes the body and next elements in the loop.
            builder()->JumpLoop(&loop_header_, level);
        }

        void LoopBuilder::BindContinueTarget() { continue_labels_.Bind(builder()); }

        SwitchBuilder::~SwitchBuilder()
        {
#ifdef DEBUG
            for (auto site : case_sites_) {
                DCHECK(!site.has_referrer_jump() || site.is_bound());
            }
#endif
        }

        void SwitchBuilder::SetCaseTarget(int index, CaseClause* clause)
        {
            BytecodeLabel& site = case_sites_.at(index);
            builder()->Bind(&site);
            if (block_coverage_builder_) {
                block_coverage_builder_->IncrementBlockCounter(clause,
                    SourceRangeKind::kBody);
            }
        }

        TryCatchBuilder::~TryCatchBuilder()
        {
            if (block_coverage_builder_ != nullptr) {
                block_coverage_builder_->IncrementBlockCounter(
                    statement_, SourceRangeKind::kContinuation);
            }
        }

        void TryCatchBuilder::BeginTry(Register context)
        {
            builder()->MarkTryBegin(handler_id_, context);
        }

        void TryCatchBuilder::EndTry()
        {
            builder()->MarkTryEnd(handler_id_);
            builder()->Jump(&exit_);
            builder()->MarkHandler(handler_id_, catch_prediction_);

            if (block_coverage_builder_ != nullptr) {
                block_coverage_builder_->IncrementBlockCounter(statement_,
                    SourceRangeKind::kCatch);
            }
        }

        void TryCatchBuilder::EndCatch() { builder()->Bind(&exit_); }

        TryFinallyBuilder::~TryFinallyBuilder()
        {
            if (block_coverage_builder_ != nullptr) {
                block_coverage_builder_->IncrementBlockCounter(
                    statement_, SourceRangeKind::kContinuation);
            }
        }

        void TryFinallyBuilder::BeginTry(Register context)
        {
            builder()->MarkTryBegin(handler_id_, context);
        }

        void TryFinallyBuilder::LeaveTry()
        {
            builder()->Jump(finalization_sites_.New());
        }

        void TryFinallyBuilder::EndTry()
        {
            builder()->MarkTryEnd(handler_id_);
        }

        void TryFinallyBuilder::BeginHandler()
        {
            builder()->Bind(&handler_);
            builder()->MarkHandler(handler_id_, catch_prediction_);
        }

        void TryFinallyBuilder::BeginFinally()
        {
            finalization_sites_.Bind(builder());

            if (block_coverage_builder_ != nullptr) {
                block_coverage_builder_->IncrementBlockCounter(statement_,
                    SourceRangeKind::kFinally);
            }
        }

        void TryFinallyBuilder::EndFinally()
        {
            // Nothing to be done here.
        }

        ConditionalControlFlowBuilder::~ConditionalControlFlowBuilder()
        {
            if (!else_labels_.is_bound())
                else_labels_.Bind(builder());
            end_labels_.Bind(builder());

            DCHECK(end_labels_.empty() || end_labels_.is_bound());
            DCHECK(then_labels_.empty() || then_labels_.is_bound());
            DCHECK(else_labels_.empty() || else_labels_.is_bound());

            // IfStatement requires a continuation counter, Conditional does not (as it
            // can only contain expressions).
            if (block_coverage_builder_ != nullptr && node_->IsIfStatement()) {
                block_coverage_builder_->IncrementBlockCounter(
                    node_, SourceRangeKind::kContinuation);
            }
        }

        void ConditionalControlFlowBuilder::JumpToEnd()
        {
            DCHECK(end_labels_.empty()); // May only be called once.
            builder()->Jump(end_labels_.New());
        }

        void ConditionalControlFlowBuilder::Then()
        {
            then_labels()->Bind(builder());
            if (block_coverage_builder_ != nullptr) {
                block_coverage_builder_->IncrementBlockCounter(block_coverage_then_slot_);
            }
        }

        void ConditionalControlFlowBuilder::Else()
        {
            else_labels()->Bind(builder());
            if (block_coverage_builder_ != nullptr) {
                block_coverage_builder_->IncrementBlockCounter(block_coverage_else_slot_);
            }
        }

    } // namespace interpreter
} // namespace internal
} // namespace v8
